package com.sshine.solon.rabbitmq.builder;

import com.rabbitmq.client.BuiltinExchangeType;
import com.sshine.solon.rabbitmq.RabbitAdmin;
import com.sshine.solon.rabbitmq.RabbitmqException;
import com.sshine.solon.rabbitmq.annotation.Argument;
import com.sshine.solon.rabbitmq.annotation.Queue;
import com.sshine.solon.rabbitmq.annotation.QueueBinding;
import com.sshine.solon.rabbitmq.annotation.RabbitListener;
import com.sshine.solon.rabbitmq.core.Binding;
import com.sshine.solon.rabbitmq.core.QueueBuilder;
import com.sshine.solon.rabbitmq.service.Exchange;
import com.sshine.solon.rabbitmq.service.RabbitMessageHandler;
import org.noear.snack.ONode;
import org.noear.solon.core.BeanBuilder;
import org.noear.solon.core.BeanWrap;
import org.noear.solon.core.event.EventBus;

import java.util.*;

/**
 * @author sshine
 * @date 2022/7/20
 */
public class RabbitListenerBeanBuilder implements BeanBuilder<RabbitListener> {
    /**
     * 构建
     *
     * @param clz  类
     * @param bw
     * @param anno 注解
     */
    @Override
    public void doBuild(Class<?> clz, BeanWrap bw, RabbitListener anno) throws Throwable {
        String[] queues = anno.queues();
        Queue[] queuesToDeclare = anno.queuesToDeclare();
        QueueBinding[] bindings = anno.bindings();
        List<String> queueListenList = new ArrayList<String>();
        if (queues.length > 0) {
            for (String queue : queues) {
                queueListenList.add(queue);
            }
        }else {
            if (queuesToDeclare.length > 0) {
                if (queues.length > 0) {
                    throw new RabbitmqException(
                            "@RabbitListener can have only one of 'queues', 'queuesToDeclare', or 'bindings'");
                }
                for (Queue queue : queuesToDeclare) {
                    com.sshine.solon.rabbitmq.core.Queue built = doBuild0(queue);
                    queueListenList.add(built.getName());
                }
            }else {
                if (bindings.length > 0) {
                    if (queues.length > 0 || queuesToDeclare.length > 0) {
                        throw new RabbitmqException(
                                "@RabbitListener can have only one of 'queues', 'queuesToDeclare', or 'bindings'");
                    }
                    for (QueueBinding binding : bindings) {
                        doBuild0(binding,queueListenList);
                    }
                }
            }
        }
        if (bw.raw() instanceof RabbitMessageHandler) {
            RabbitAdmin.addToContainer(bw.raw(),queueListenList);
        }
    }

    private Exchange doBuild0(com.sshine.solon.rabbitmq.annotation.Exchange anno){
        Map<String, Object> argument = resolveArgument(anno.argument());
        ExchangeBuilder exchangeBuilder = new ExchangeBuilder(anno.name(), anno.type()).autoDelete(anno.autoDelete()).declare(anno.declare()).delayed(anno.delayed()).durable(anno.durable())
                .internal(anno.internal()).withArguments(argument);
        if (anno.ignoreDeclarationExceptions()) {
            exchangeBuilder.ignoreDeclarationExceptions();
        }
        Exchange exchange = exchangeBuilder.build();
        EventBus.pushAsyn(exchange);
        return exchange;
    }

    private com.sshine.solon.rabbitmq.core.Queue doBuild0(Queue anno) {
        com.sshine.solon.rabbitmq.core.Queue queue = QueueBuilder.nonDurable(anno.name()).autoDelete(anno.autoDelete())
                .exclusive(anno.exclusive()).withArguments(resolveArgument(anno.argument())).build();
        queue.setShouldDeclare(anno.declare());
        queue.setIgnoreDeclarationExceptions(anno.ignoreDeclarationExceptions());
        EventBus.pushAsyn(queue);
        return queue;
    }

    private void doBuild0(QueueBinding binding,List<String> queueListenList) {
        Exchange exDeclare = doBuild0(binding.exchange());
        com.sshine.solon.rabbitmq.core.Queue queDeclare = doBuild0(binding.queue());
        queueListenList.add(queDeclare.getName());
        Map<String, Object> argument = resolveArgument(binding.argument());
        final List<String> routingKeys;
        if (exDeclare.getType().equals(BuiltinExchangeType.FANOUT) || binding.key().length == 0) {
            routingKeys = Collections.singletonList("");
        }
        else {
            final int length = binding.key().length;
            routingKeys = new ArrayList<>();
            for (int i = 0; i < length; ++i) {
                routingKeys.add(binding.key()[i]);
            }
        }
        for (String routingKey : routingKeys) {
            Binding actualBinding = BindingBuilder.bind(queDeclare).to(exDeclare).with(routingKey).and(argument);
            actualBinding.setShouldDeclare(binding.declare());
            actualBinding.setIgnoreDeclarationExceptions(binding.ignoreDeclarationExceptions());
            EventBus.pushAsyn(actualBinding);
        }
    }

    private Map<String,Object> resolveArgument(Argument... arguments) {
        Map<String, Object> map = new HashMap<>();
        for (Argument argument : arguments) {
            String value = argument.value();
            map.put(argument.name(), resolve(value, argument.type()));
        }
        return map;
    }

    private Object resolve(String value,Class type) {
        if (type == int.class || type == Integer.class) {
            return Integer.valueOf(value);
        }else if (type == byte.class || type == Byte.class) {
            return Byte.valueOf(value);
        }else if (type == short.class || type == Short.class) {
            return Short.valueOf(value);
        }else if (type == long.class || type == Long.class) {
            return Long.valueOf(value);
        }else if (type == boolean.class || type == Boolean.class) {
            return Boolean.valueOf(value);
        }else if (type == float.class || type == Float.class) {
            return Float.valueOf(value);
        }else if (type == double.class || type == Double.class) {
            return Double.valueOf(value);
        }else if (type == char.class || type == Character.class) {
            return value.toCharArray();
        }else {
            return ONode.deserialize(value, type);
        }
    }
}
